package org.huang.zhen;

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;


public class GameMap implements Runnable {
    public static final int GAME_MAP_SIZE_ROW = 24;
    public static final int GAME_MAP_SIZE_COL = 14;
    private static final int GAME_PAUSE_CYCLE = 50;
    private static final int GAME_MAP_REFRESH_CYCLE = 600;

    private static final Logger logger = LogManager.getLogger(GameMap.class);

    private volatile int [][] mMap;
    private volatile Block mBlk;
    private volatile int mRefreshCycle;
    private GAME_STATE mGameState;
    private Thread mRefreshThrd;
    private GameMapListener mListener;
    private Position mPos;

    public enum GAME_STATE {
        RUNNING,
        PAUSE,
        STOPPED
    }

    public enum GAME_BLOCK_HORIZONTAL_DIRECTION {
        LEFT,
        RIGHT
    }

    public class Position {
        public int X;
        public int Y;

        public Position(int x, int y) {
            X = x;
            Y = y;
        }
    }

    public interface GameMapListener {
        public void onBlockSettled();
        public void onMapFull();
        public void onGameMapChanged(final int [][]gameMap);
        public void onLinesRemoved(int lines);
    }

    public GameMap(GameMapListener listener, int speed) {
        mMap = new int[GAME_MAP_SIZE_ROW][GAME_MAP_SIZE_COL];;
        mGameState = GAME_STATE.STOPPED;
        mRefreshCycle = GAME_MAP_REFRESH_CYCLE;
        mListener = listener;
        mRefreshCycle = speed;
    }

    public int getSpeed() { return mRefreshCycle; }

    @Override
    public void run() {
        try {
            while(mGameState != GAME_STATE.STOPPED) {
                if (mGameState == GAME_STATE.PAUSE) {
                    logger.debug("PAUSE...");
                    Thread.sleep(GAME_PAUSE_CYCLE);
                    continue;
                }
                logger.debug("Refresh game map...");
                refreshMap();
                Thread.sleep(mRefreshCycle);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public Thread start() {
        mGameState = GAME_STATE.RUNNING;
        if (mRefreshThrd == null) {
            mRefreshThrd = new Thread(this);
            mRefreshThrd.start();
        }
        return mRefreshThrd;
    }

    public void stop() throws InterruptedException {
        mGameState = GAME_STATE.STOPPED;
        if (mRefreshThrd != null) {
            mRefreshThrd.join();
        }
        logger.debug("Game refresh stopped.");
    }

    public void pause() {
        mGameState = GAME_STATE.PAUSE;
    }

    public void resume() {
        if (mGameState == GAME_STATE.PAUSE) {
            mGameState = GAME_STATE.RUNNING;
        }
    }

    public void insertBlock(Block blk) {
        // TODO要控制当前有方块的时候，不能insert
        mBlk = blk;
        calculateBlkInitPos(blk);
    }

    private List<Integer> checkFullLines() {
        List<Integer> fullLines = new ArrayList<Integer>();
        for (int i = GAME_MAP_SIZE_ROW - 1; i >= 0; i--) {
            boolean isFull = true, isEmptyLine = true;
            for (int j = GAME_MAP_SIZE_COL - 1; j >= 0; j--) {
                if (mMap[i][j] == 0) {
                    isFull = false;
                }  else {
                    isEmptyLine = false;
                }
            }
            // 记录满行
            if (isFull) {
                fullLines.add(i);
            }
            // 出现空行，不再检测
            if (isEmptyLine) {
                break;
            }
        }
        return fullLines;
    }

    private int removeFullLines() {
        List<Integer> fullLines = checkFullLines();
        Collections.reverse(fullLines);
        for (Integer line: fullLines) {
            for (int j = 0; j < GAME_MAP_SIZE_COL; j++) {
                for (int i = line.intValue(); i > 0; i--) {
                    mMap[i][j] = mMap[i -1][j];
                }
            }
        }
        return fullLines.size();
    }

    private void copyBlk2GameMap(int [][]gameMap) {
        int gameMapX = mPos.X;
        int gameMapY = mPos.Y;
        int [][]blkMap = mBlk.getMap();
        for (int i = 0; i < mBlk.getHeight(); i++) {
            for (int j = 0; j < mBlk.getWidth(); j++) {
                if (gameMapX - i < 0 ) {
                    break;
                }
                int blkMapBit = blkMap[mBlk.getHeight() - 1 - i][j];
                if (gameMapY + j >= GAME_MAP_SIZE_COL || gameMapY + j < 0) {
                    continue;
                }
                int gameMapBit = gameMap[gameMapX - i][gameMapY + j];
                gameMap[gameMapX - i][gameMapY + j] = blkMapBit | gameMapBit;
            }
        }
    }

    private void refreshMap() {
        int [][]tmpGameMap = null;
        if (canBeFall()) {
            mPos.X++;
            tmpGameMap = cloneMap();
            copyBlk2GameMap(tmpGameMap);
        } else {
            // 如果方块不能再移动，方块的数据copy到游戏map中
            copyBlk2GameMap(mMap);
            int fullLines = removeFullLines();
            if (mListener != null) {
                mListener.onBlockSettled();
                if (isFull()) {
                    mListener.onMapFull();
                }
                mListener.onLinesRemoved(fullLines);
            }
            tmpGameMap = cloneMap();
        }

        notifyMapChanged(tmpGameMap);
    }

    private synchronized void notifyMapChanged(int [][]map) {
        if (mListener != null) {
            mListener.onGameMapChanged(map);
        }
    }

    private int[][] cloneMap() {
        int [][]tmpGameMap = new int[GAME_MAP_SIZE_ROW][GAME_MAP_SIZE_COL];
        for (int i = 0; i < GAME_MAP_SIZE_ROW; i++) {
            for (int j = 0; j < GAME_MAP_SIZE_COL; j++) {
                tmpGameMap[i][j] = mMap[i][j];
            }
        }
        return tmpGameMap;
    }

    private boolean canBeFall() {
        int gameMapX = mPos.X + 1;
        int gameMapY = mPos.Y;
        // 边缘
        if (mMap.length <= gameMapX) {
            return false;
        }

        int [][]blkMap = mBlk.getMap();
        for (int i = 0; i < mBlk.getHeight(); i++) {
            for (int j = 0; j < mBlk.getWidth(); j++) {
                if (gameMapX - i < 0) {
                    break;
                }
                // 方块左右移动，其纵坐标值域为[-4, 17+4]
                if (gameMapY + j >= GAME_MAP_SIZE_COL || gameMapY + j < 0) {
                    continue;
                }

                if ((mMap[gameMapX - i][gameMapY + j] & blkMap[mBlk.getHeight() - 1 - i][j]) == 1) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * 计算Block进入游戏界面的初始位置
     * @param blk
     */
    private void calculateBlkInitPos(Block blk) {
        int y = (GAME_MAP_SIZE_COL - blk.getWidth() )/2;
        mPos = new Position(0, y);
    }


    public GAME_STATE getState() {
        return mGameState;
    }

    private boolean isFull() {
        for (int i = 0; i < GAME_MAP_SIZE_COL; i++) {
            if (mMap[0][i] == 1) {
                return true;
            }
        }

        return false;
    }

    public static String toString(int [][]gameMap) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < gameMap.length; i++) {
            for (int j = 0; j < gameMap[i].length; j++) {
                sb.append(gameMap[i][j]).append(" ");
            }
            sb.append("\n");
        }
        return sb.toString();
    }

    public void setDownSpeed(int speed) {
        mRefreshCycle = speed;
    }

    private boolean isBlockMove2Left(Block blk) {
        // 如果方块矩阵边沿与游戏矩阵边沿重合
        if (mPos.Y <= 0) {
            int blkCol = 0 - mPos.Y;
            for (int i = 0; i < blk.getHeight(); i++) {
                if (blk.getMap()[i][blkCol] == 1) {
                    return false;
                }
            }
            return true;
        }

        for (int j = 0; j < blk.getWidth(); j++) {
            for (int i = 0; i < blk.getHeight(); i++) {
                int mapX = mPos.X - blk.getHeight() + i + 1;
                if (mapX < 0) {
                    continue;
                }
                if ((blk.getMap()[i][j] & mMap[mapX][mPos.Y - 1 + j]) ==1){
                    return false;
                }
            }
        }
        return true;
    }

    private boolean isBlockMove2Right(Block blk) {
        // 坐标换算为右下角计算
        int y = mPos.Y + blk.getWidth() - 1;
        int x = mPos.X;
        // 如果方块矩阵边沿与游戏矩阵边沿重合
        if ( y >= GAME_MAP_SIZE_COL - 1) {
            int overRightCols = y - GAME_MAP_SIZE_COL + 1;
            int detectColIndex = blk.getWidth() - overRightCols - 1;
            for (int i = 0; i < blk.getHeight(); i++) {
                if (blk.getMap()[i][detectColIndex] == 1) {
                    return false;
                }
            }
            return true;
        }

        int mapTopRightX = mPos.X - blk.getHeight() + 1;
        int mapTopRightY = mPos.Y + blk.getWidth();
        for (int j = blk.getWidth() - 1; j >= 0; j--, mapTopRightY--) {
            for (int i = 0; i < blk.getHeight(); i++) {
                if (mapTopRightX + i < 0) {
                    continue;
                }
                
                if ((blk.getMap()[i][j] & mMap[mapTopRightX + i][mapTopRightY]) == 1) {
                    return false;
                }
            }
        }
        return true;
    }

    private void refreshMapImmediately() {
        int [][]map = cloneMap();
        copyBlk2GameMap(map);
        notifyMapChanged(map);
    }

    public boolean changeBlockHorizonPos(GAME_BLOCK_HORIZONTAL_DIRECTION direction) {
        if (direction == GAME_BLOCK_HORIZONTAL_DIRECTION.LEFT) {
            if (isBlockMove2Left(mBlk)) {
                mPos.Y--;
                logger.info("Move left");
                refreshMapImmediately();
                return true;
            }
        } else {
            if (isBlockMove2Right(mBlk)) {
                mPos.Y++;
                logger.info("Move right");
                refreshMapImmediately();
                return true;
            }
        }
        return false;
    }

    public void rotateBlock() {
        mBlk.rotate();
        refreshMapImmediately();
    }

}
